﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data;

namespace DataExtractionLanguage
{
    public class JoinPart
    {
        public SourceList LeftPart { get; set; }
        public SourceList RightPart { get; set; }

        internal void AddPart(SourceList srcLst)
        {
            if (LeftPart == null)
                LeftPart = srcLst;
            else if (RightPart == null)
                RightPart = srcLst;
            else
                throw new InvalidOperationException();
        }

        private int GetSourceListIndex(SourceList srcLst)
        {
            return fromClause.Sources.IndexOf(srcLst);
        }

        internal SourceList ConfigureResultSourceList(JoiningTypeEnum joiningType)
        {
            SourceList resultSrcLst = new SourceList();
            resultSrcLst.Result = new DataTable();

            SourceList tmpSrcLst = null;

            int idx1 = GetSourceListIndex(LeftPart);
            int idx2 = GetSourceListIndex(RightPart);

            // Adjust left and right part up to their index, In other word up to how they are written in the source code.
            if (idx2 < idx1)
            {
                tmpSrcLst = RightPart;
                RightPart = LeftPart;
                LeftPart = tmpSrcLst;
            }
            else
            {
                tmpSrcLst = LeftPart;
                LeftPart = RightPart;
                RightPart = tmpSrcLst;
            }

            // readjust left and right parts according to JoiningType
            if (joiningType == JoiningTypeEnum.RightOuterJoin)
            {
                tmpSrcLst = LeftPart;
                LeftPart = RightPart;
                RightPart = tmpSrcLst;

                resultSrcLst.Result = CreateEmptyDataTable(RightPart, LeftPart);
                resultSrcLst.Result.TableName = resultSrcLst.Alias = string.Join("_", RightPart.Alias, LeftPart.Alias);
            }
            else
            {
                resultSrcLst.Result = CreateEmptyDataTable(LeftPart, RightPart);
                resultSrcLst.Result.TableName = resultSrcLst.Alias = string.Join("_", LeftPart.Alias, RightPart.Alias);
            }
            return resultSrcLst;
        }

        private DataTable CreateEmptyDataTable(SourceList leftSrcLst, SourceList rightSrcLst)
        {
            DataTable dataTbl = new DataTable();
            foreach (DataColumn col in LeftPart.Result.Columns)
                dataTbl.Columns.Add(col.ColumnName, col.DataType, col.Expression);
            foreach (DataColumn col in RightPart.Result.Columns)
                dataTbl.Columns.Add(col.ColumnName, col.DataType, col.Expression);

            return dataTbl;
        }
        private FromClause fromClause;

        internal SourceList ApplyJoin(JoinClause joinClause, FromClause fromClause)
        {
            this.fromClause = fromClause;
            SourceList resultSrcLst = ConfigureResultSourceList(joinClause.JoiningType);

            List<object> tmpNewRow = null;
            LeftPart.ResultRowPosition = 0;
            bool anyRowMatched = false;

            foreach (DataRow leftTableRow in LeftPart.Result.Rows)
            {
                RightPart.ResultRowPosition = 0;
                foreach (DataRow rightTableRow in RightPart.Result.Rows)
                {
                    if (EvaluateCondition(joinClause.LinkageExp))
                    {
                        anyRowMatched = true;
                        tmpNewRow = new List<object>();
                        tmpNewRow.AddRange(leftTableRow.ItemArray);
                        tmpNewRow.AddRange(rightTableRow.ItemArray);
                        resultSrcLst.Result.Rows.Add(tmpNewRow.ToArray());
                    }
                    RightPart.ResultRowPosition++;
                }

                if (!anyRowMatched && joinClause.JoiningType != JoiningTypeEnum.InnerJoin)
                {
                    anyRowMatched = false;
                    tmpNewRow = new List<object>();
                    tmpNewRow.AddRange(leftTableRow.ItemArray);
                    resultSrcLst.Result.Rows.Add(tmpNewRow.ToArray());

                }
                LeftPart.ResultRowPosition++;
            }

            return resultSrcLst;
        }

        private ExpressionTerm EvaluateExpressionTerm(ExpressionTerm exp)
        {
            ExpressionTerm exp0 = exp.Clone();
            ExpressionTerm curExp = exp0;
            ExpressionTerm prev = null;
            ExpressionTerm prevPrev = null;

            while (curExp != null)
            {
                if (curExp.TermType.In(TokenTypeEnum.String, TokenTypeEnum.Number))
                {
                    prevPrev = prev;
                    prev = curExp;
                    curExp = curExp.Next;
                }
                else if (curExp.TermType == TokenTypeEnum.Term)
                {
                    SourceList srcLst = fromClause.GetSourceByAlias(curExp.Term.SourceAlias);
                    DataColumn termCol = srcLst.Result.Columns[curExp.Term.ColumnAlias];
                    object cellValue = srcLst.Result.Rows[srcLst.ResultRowPosition][termCol];

                    if (cellValue != null)
                    {
                        if (cellValue is int)
                            curExp.TermType = TokenTypeEnum.Number;
                        else if (cellValue is string)
                            curExp.TermType = TokenTypeEnum.String;
                        else if (cellValue is bool)
                            curExp.TermType = TokenTypeEnum.Bool;

                        curExp.Value = cellValue;
                    }
                    else
                        throw new Exception("لازم تراجع هون");

                    prevPrev = prev;
                    prev = curExp;
                    curExp = curExp.Next;
                }
                else if (curExp.TermType == TokenTypeEnum.OpNot)
                {
                    if (prev == null)
                        throw new Exception("خطأ");

                    if (prev.TermType == TokenTypeEnum.Number)
                    {
                        if (prev.TermType == TokenTypeEnum.OpNot)
                            prev.Value = !Convert.ToBoolean(prev.Value);
                    }

                    prev.Next = curExp.Next;
                    curExp = prev.Next;
                }
                else if (curExp.TermType.In(TokenTypeEnum.OpPlus, TokenTypeEnum.OpMinus, TokenTypeEnum.OpMultiple, TokenTypeEnum.OpDivide, TokenTypeEnum.OpPower, TokenTypeEnum.OpAnd, TokenTypeEnum.OpOr, TokenTypeEnum.OpGreater, TokenTypeEnum.OpGreaterOrEqual, TokenTypeEnum.OpLess, TokenTypeEnum.OpLessOrEqual, TokenTypeEnum.OpEqualEqual, TokenTypeEnum.OpNotEqual))
                {
                    if (prevPrev.TermType == TokenTypeEnum.Number && prev.TermType == TokenTypeEnum.Number)
                    {
                        if (curExp.TermType == TokenTypeEnum.OpPlus)
                            prevPrev.Value = Convert.ToInt32(prevPrev.Value) + Convert.ToInt32(prev.Value);
                        else if (curExp.TermType == TokenTypeEnum.OpMinus)
                            prevPrev.Value = Convert.ToInt32(prevPrev.Value) - Convert.ToInt32(prev.Value);
                        else if (curExp.TermType == TokenTypeEnum.OpMultiple)
                            prevPrev.Value = Convert.ToInt32(prevPrev.Value) * Convert.ToInt32(prev.Value);
                        else if (curExp.TermType == TokenTypeEnum.OpDivide)
                        {
                            if (Convert.ToInt32(prev.Value) == 0)
                                throw new Exception("خطأ");

                            prevPrev.Value = Convert.ToInt32(prevPrev.Value) / Convert.ToInt32(prev.Value);
                        }
                        else if (curExp.TermType == TokenTypeEnum.OpOr)
                            if (Convert.ToInt32(prevPrev.Value) == 0 && Convert.ToInt32(prev.Value) == 0)
                                prevPrev.Value = 0;
                            else
                                prevPrev.Value = 1;

                        else if (curExp.TermType == TokenTypeEnum.OpAnd)
                            if (Convert.ToInt32(prevPrev.Value) != 0 && Convert.ToInt32(prev.Value) != 0)
                                prevPrev.Value = 1;
                            else
                                prevPrev.Value = 0;

                        else if (curExp.TermType == TokenTypeEnum.OpPower)
                            prevPrev.Value = Convert.ToDecimal(Math.Pow(Convert.ToDouble(prevPrev.Value), Convert.ToDouble(prev.Value)));
                        else if (curExp.TermType == TokenTypeEnum.OpEqualEqual)
                            prevPrev.Value = Convert.ToBoolean(prevPrev.Value == prev.Value);
                        else if (curExp.TermType == TokenTypeEnum.OpNotEqual)
                            prevPrev.Value = Convert.ToBoolean(prevPrev.Value != prev.Value);
                        else if (curExp.TermType == TokenTypeEnum.OpGreater)
                            prevPrev.Value = Convert.ToBoolean(Convert.ToInt32(prevPrev.Value) > Convert.ToInt32(prev.Value));
                        else if (curExp.TermType == TokenTypeEnum.OpGreaterOrEqual)
                            prevPrev.Value = Convert.ToDecimal(Convert.ToInt32(prevPrev.Value) >= Convert.ToInt32(prev.Value));
                        else if (curExp.TermType == TokenTypeEnum.OpLess)
                            prevPrev.Value = Convert.ToDecimal(Convert.ToInt32(prevPrev.Value) < Convert.ToInt32(prev.Value));
                        else if (curExp.TermType == TokenTypeEnum.OpLessOrEqual)
                            prevPrev.Value = Convert.ToDecimal(Convert.ToInt32(prevPrev.Value) <= Convert.ToInt32(prev.Value));
                    }
                    else if (prevPrev.TermType == TokenTypeEnum.String && prev.TermType == TokenTypeEnum.String)
                    {
                        if (curExp.TermType == TokenTypeEnum.OpEqualEqual)
                        {
                            prevPrev.Value = Convert.ToBoolean(prevPrev.Value.ToString() == prev.Value.ToString());
                            prevPrev.TermType = TokenTypeEnum.Bool;
                        }
                        else if (curExp.TermType == TokenTypeEnum.OpPlus)
                            prevPrev.Value = prevPrev.Value.ToString() + prev.Value.ToString();
                    }
                    else if (prevPrev.TermType == TokenTypeEnum.Bool && prev.TermType == TokenTypeEnum.Bool)
                    {
                        if (curExp.TermType == TokenTypeEnum.OpAnd)
                        {
                            prevPrev.Value = Convert.ToBoolean(prevPrev.Value) & Convert.ToBoolean(prev.Value);
                            prevPrev.TermType = TokenTypeEnum.Bool;
                        }
                        else if (curExp.TermType == TokenTypeEnum.OpOr)
                        {
                            prevPrev.Value = Convert.ToBoolean(prevPrev.Value) | Convert.ToBoolean(prev.Value);
                            prevPrev.TermType = TokenTypeEnum.Bool;
                        }
                    }

                    ExpressionTerm tmp = exp0;
                    if (prevPrev == exp0)
                        tmp = null;
                    else
                        while (tmp.Next != prevPrev)
                            tmp = tmp.Next;

                    prevPrev.Next = curExp.Next;
                    prev = prevPrev;
                    prevPrev = tmp;
                    curExp = prev.Next;
                }
            }

            return exp0;
        }

        private bool EvaluateCondition(ExpressionTerm expression)
        {
            if (expression == null)
                return true;

            ExpressionTerm whereRst = EvaluateExpressionTerm(expression);

            if (whereRst.Value is bool)
                return Convert.ToBoolean(whereRst.Value);

            return false;
        }
    }
}
